﻿using System;
using System.Data.Odbc;
using System.Linq;
using System.Text;

namespace Centido.Core
{
    /// <summary>
    ///   A set of table update operations.
    /// </summary>
    public static class TableUpdateOperations
    {
        /// <summary>
        ///   Updates a row with the specified id and the expected row version in the table.
        /// </summary>
        /// <param name="table">The target table.</param>
        /// <param name = "rowId">The id of the row.</param>
        /// <param name="exprectedRowVersion">The expected row version.</param>
        /// <param name="dataSource">The target data source.</param>
        /// <param name = "values">The list of the column values to be updated.</param>
        /// <returns>Returns true if a row with the specified id and the specified version (if it was specified) was found; otherwise, false.</returns>
        private static bool UpdateRowImplementation(Table table, Guid rowId, Guid? exprectedRowVersion, DataSource dataSource, params ColumnValue[] values)
        {
            // If there is nothing to update
            if (values == null || values.Length == 0)
                throw new ArgumentException("No table columns to be updated were specified.");

            // List of comma separated assignments
            var assignmentList = new StringBuilder();

            // Including all column except binary and text columns
            values.ToList().ForEach(value =>
                    assignmentList.Append(assignmentList.Length != 0 ? "," : string.Empty)
                    .Append(value.ColumnName).Append("=?"));

            var rowWasFound = false;

            if (assignmentList.Length > 0)
            {
                rowWasFound = table.Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "update {0} set {1} where {2}=?",
                            table.GetDataSourceName(dataSource),
                            assignmentList,
                            Db.Tables.UserTable.Col.Id);

                        // If the expected row version was specified
                        if (exprectedRowVersion.HasValue)
                            cmd.CommandText += string.Format(" and {0}=?",
                                Db.Tables.UserTable.Col.RowVersion);

                        // Adding parameter values
                        values.ToList().ForEach(
                            value =>
                            cmd.Parameters.Add(
                                table.Domain.DbAccess.CreateParameter(
                                    value.ColumnName, value.Column.ValueOdbcType, DbValue.GetDbValue(value.Value))));

                        cmd.Parameters.Add(
                            table.Domain.DbAccess.CreateParameter("id", OdbcType.Binary, rowId.ToByteArray()));

                        // If the expected row version was specified
                        if (exprectedRowVersion.HasValue)
                            cmd.Parameters.Add(
                                table.Domain.DbAccess.CreateParameter("row_version", OdbcType.Binary,
                                                                exprectedRowVersion.Value.ToByteArray()));
                    }) != 0;
            }

            return rowWasFound;
        }


        /// <summary>
        ///   Updates the columns of the row with the specified id.
        /// </summary>
        /// <remarks>
        /// <para>The values of such system columns as UpdatedOn, UpdatedBy and RowVersion are
        /// reset after executing this function.</para>
        /// <para>The current user has to have the update permission on this table in order to execute this function.</para>
        /// </remarks>
        ///<param name="table">The target table.</param>
        ///<param name = "rowId">The id of the row to be updated.</param>
        /// <param name = "values">The list of new column values.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when one of the column values is null.</exception>
        /// <exception cref="ArgumentException">Thrown when no table column to be updated were specified.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no update permission on the table.</exception>
        /// <returns>Returns true if a row with the specified id was found; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to change the owner of a row.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///
        ///             //Inserting a row
        ///             Console.WriteLine("Inserting a row");
        /// 
        ///             var id = table.InsertRow();
        /// 
        ///             //Reading the owner of the row
        ///             Console.WriteLine("Owner = {0}", DbValue.GetString(table.SystemColumns.Owner.Read(id)));
        /// 
        ///             //Changing the owner
        ///             Console.WriteLine("Changing the owner");
        /// 
        ///             table.UpdateRow(id, table.SystemColumns.Owner.Value("another_owner"));
        /// 
        ///             //Reading the owner of the row
        ///             Console.WriteLine("Owner = {0}", DbValue.GetString(table.SystemColumns.Owner.Read(id)));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static bool UpdateRow(this Table table, Guid rowId, params ColumnValue[] values)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return UpdateRowImplementation(table, rowId, null, DataSource.UserView, values);
        }


        /// <summary>
        ///   Updates the columns of the row with the specified id and the specified row version. 
        /// </summary>
        /// <remarks>
        /// <para>Updating specifying the version of the row guarantees
        /// that the row has not changed since the last selection.</para>
        /// <para>The values of such system columns as UpdatedOn, UpdatedBy and RowVersion are
        /// reset after executing this function.</para>
        /// <para>The current user has to have the update permission on this table in order to execute this function.</para>
        /// </remarks>
        ///<param name="table">The target table.</param>
        ///<param name="rowId">The id of the row to be updated.</param>
        /// <param name="expectedRowVersion">The expected row version.</param>
        /// <param name="values">The list of new column values.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when one of the column values is null.</exception>
        /// <exception cref="ArgumentException">Thrown when no table column to be updated were specified.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no update permission on the table.</exception>
        /// <returns>Returns true if a row with the specified id and row version was found;
        /// otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to update a row specifying the id and the version of the row.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             //Reading the row version
        ///             var rowVersion = table.SystemColumns.RowVersion.Read(id).GetGuid().Value;
        /// 
        ///             //Updating the row specifying the row version
        ///             var updateResult = table.UpdateRow(id, rowVersion,
        ///                 table.SystemColumns.Owner.Value("new_owner"));
        /// 
        ///             Console.WriteLine("The first update: result = {0}", updateResult);
        ///             Console.WriteLine("The row: owner = {0}", table.SystemColumns.Owner.Read(id).GetString());
        /// 
        ///             //Updating the row specifying the outdated row version
        ///             updateResult = table.UpdateRow(id, rowVersion,
        ///                 table.SystemColumns.Owner.Value("another_owner"));
        /// 
        ///             Console.WriteLine("The second update: result = {0}", updateResult);
        ///             Console.WriteLine("The row: owner = {0}", table.SystemColumns.Owner.Read(id).GetString());             
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static bool UpdateRow(this Table table, 
            Guid rowId, Guid expectedRowVersion, params ColumnValue[] values)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return UpdateRowImplementation(table, rowId, expectedRowVersion, DataSource.UserView, values);
        }


        /// <summary>
        ///   Updates the columns of the row with the specified id.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the update permission on the specified
        /// data source in order to call this function.
        /// </remarks>
        /// <param name="table">The target table.</param>
        /// <param name="rowId">The id of the row to be updated.</param>
        /// <param name="dataSource">The target data source.</param>
        /// <param name="values">The list of new column values.</param>
        /// <returns>Returns true if a row with the specified id was found;
        /// otherwise, false.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when one of the column values is null.</exception>
        /// <exception cref="ArgumentException">Thrown when no table column to be updated were specified.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current
        /// user has no update permission on the specified data source.</exception>
        /// <example>
        ///   The following example shows how to change the owner of a row.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///
        ///             //Inserting a row
        ///             Console.WriteLine("Inserting a row");
        /// 
        ///             var id = table.InsertRow();
        /// 
        ///             //Reading the owner of the row
        ///             Console.WriteLine("Owner = {0}", DbValue.GetString(table.SystemColumns.Owner.Read(id)));
        /// 
        ///             //Changing the owner
        ///             Console.WriteLine("Changing the owner");
        /// 
        ///             table.UpdateRow(id, DataSource.UserView, table.SystemColumns.Owner.Value("another_owner"));
        /// 
        ///             //Reading the owner of the row
        ///             Console.WriteLine("Owner = {0}", DbValue.GetString(table.SystemColumns.Owner.Read(id)));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static bool UpdateRow(this Table table, Guid rowId,
            DataSource dataSource, params ColumnValue[] values)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return UpdateRowImplementation(table, rowId, null, dataSource, values);
        }


        /// <summary>
        ///   Updates the columns of the row with the specified id.
        /// </summary>
        /// <remarks>
        ///   The current user has to have the update permission on the
        /// specified data source in order to call this function.
        /// </remarks>
        /// <param name="table">The target table.</param>
        /// <param name="rowId">The id of the row to be updated.</param>
        /// <param name="expectedRowVersion">The expected row version.</param>
        /// <param name="dataSource">The target data source.</param>
        /// <param name="values">The list of new column values.</param>
        /// <returns>Returns true if a row with the specified id was found; otherwise, false.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when one of the column values is null.</exception>
        /// <exception cref="ArgumentException">Thrown when no table column to be updated were specified.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current
        /// user has no update permission on the specified data source.</exception>
        /// <example>
        ///   The following example shows how to update a row specifying the id and the version of the row.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             //Reading the row version
        ///             var rowVersion = table.SystemColumns.RowVersion.Read(id).GetGuid().Value;
        /// 
        ///             //Updating the row specifying the row version
        ///             var updateResult = table.UpdateRow(id, rowVersion, DataSource.UserView,
        ///                 table.SystemColumns.Owner.Value("new_owner"));
        /// 
        ///             Console.WriteLine("The first update: result = {0}", updateResult);
        ///             Console.WriteLine("The row: owner = {0}", table.SystemColumns.Owner.Read(id).GetString());
        /// 
        ///             //Updating the row specifying the outdated row version
        ///             updateResult = table.UpdateRow(id, rowVersion, DataSource.UserView,
        ///                 table.SystemColumns.Owner.Value("another_owner"));
        /// 
        ///             Console.WriteLine("The second update: result = {0}", updateResult);
        ///             Console.WriteLine("The row: owner = {0}", table.SystemColumns.Owner.Read(id).GetString());             
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static bool UpdateRow(this Table table, Guid rowId,
            Guid expectedRowVersion, DataSource dataSource, params ColumnValue[] values)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return UpdateRowImplementation(table, rowId, expectedRowVersion, dataSource, values);
        }
    }
}
